/**
 * @author oki
 * 
 * A simple robot to play the brain buddy game
 * 
 */

import ij.ImagePlus;
import ij.process.ImageConverter;
import ij.process.ImageProcessor;

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;

import javax.imageio.ImageIO;

public class BrainBuddy {

	public static final int sampleCount = 4;
	private static final String baseScreenFileName = "baseScreen.png";

	private static final int firstWait = 7000;
	private static final int waitNextLevel = 900; // wait for 1.4 seconds
	private static final int waitClick = 200; // click and wait for 1 second for
												// another click
	private static final int borderThickness = 5;

	private static final int gridBorderColor = -1; // we transform the image so
													// that the borders are all
													// white

	// screen resolution constants
	private static final int screenWidth = 1440;
	private static final int screenHeight = 900;

	// game screen start and end
	private static final Point gameStartPoint = new Point(365, 258);
	private static final Point gameEndPoint = new Point(768, 625);

	// when we examine the game it is obvious that the game is static
	private static Point[] screenStartPoints = new Point[5];
	private static Point[] screenEndPoints = new Point[5];

	private static int[] xCounts = new int[7];
	private static int[] yCounts = new int[7];

	private static int levelCounter = 0;

	private Robot robot;

	Point upperLeft;
	Point downRight;

	int xCount; // keeps the number of grids on the x coordinate
	int yCount; // keeps the number of grids on the y coordinate

	int xGridSize;
	int yGridSize;

	BufferedImage baseImage;

	ArrayList<Point> contentPoints;
	ArrayList<Color> contentPixels;
	ArrayList<Color> emptyPixels;

	ArrayList<Grid> grids;

	// constructor method
	public BrainBuddy() {
		try {
			// set the robot to carry out virtualization
			robot = new Robot();
		} catch (AWTException ex) {
			System.out.println(ex.getMessage());
		}

		upperLeft = new Point(0, 0);
		downRight = new Point(0, 0);

		// loadBaseScreen image which is captured previously
		loadBaseScreen();

		// initialize the predetermined braind buddy game screen elements
		initBrainBuddyLevels();

	}

	// initialize predetermined values of the game levels
	private void initBrainBuddyLevels() {

		screenStartPoints[0] = new Point(444, 304);
		screenStartPoints[1] = new Point(407, 312);
		screenStartPoints[2] = new Point(440, 306);
		screenStartPoints[3] = new Point(392, 296);
		screenStartPoints[4] = new Point(424, 281);
		screenStartPoints[5] = new Point();
		screenStartPoints[6] = new Point();

		screenEndPoints[0] = new Point(743, 598);
		screenEndPoints[1] = new Point(781, 591);
		screenEndPoints[2] = new Point(745, 608);
		screenEndPoints[3] = new Point(794, 614);
		screenEndPoints[4] = new Point(762, 616);
		screenEndPoints[5] = new Point();
		screenEndPoints[6] = new Point();

		xCounts[0] = 3;
		xCounts[1] = 4;
		xCounts[2] = 4;
		xCounts[3] = 5;
		xCounts[4] = 5;
		xCounts[5] = 6;
		xCounts[6] = 6;

		yCounts[0] = 3;
		yCounts[1] = 3;
		yCounts[2] = 4;
		yCounts[3] = 4;
		yCounts[4] = 5;
		yCounts[5] = 5;
		yCounts[6] = 6;

	}

	public Robot getRobot() {
		return robot;
	}

	// starts to play the game by using screenshots of the game and
	// virtualization
	public void play() {
		wait(firstWait);
		for (int i = 0; i < 30; i++) {
			Screen screen = getScreen();
			screen.solveScreen();
			// now click the dirty cells
			clickDirtyGrids(screen);
			wait(waitNextLevel);
		}

	}

	// fires events to click the dirty grid coordinates
	private void clickDirtyGrids(Screen screen) {

		ArrayList<Grid> grids = screen.getGridList();

		for (int i = 0; i < grids.size(); i++) {
			Grid grid = grids.get(i);
			if (grid.isDirty()) {
				wait(waitClick);
				Point topLeft = grid.getTopLeft();
				Point bottomRight = grid.getBottomRight();
				int clickX = topLeft.x + (bottomRight.x - topLeft.x) / 2;
				int clickY = topLeft.y + (bottomRight.y - topLeft.y) / 2;
				Main.log("click x: " + clickX + " click y: " + clickY);
				// click the calculated position

				robot.mouseMove(clickX, clickY);
				//wait(50);
				robot.mousePress(InputEvent.BUTTON1_MASK);
				//wait(10);
				robot.mouseRelease(InputEvent.BUTTON1_MASK);
				//wait(50);
			}
		}

	}

	// gets the capture of the current screen and apply edge find methods of
	// ImageJ library
	private BufferedImage getCapture() {

		Main.log("capture started");

		BufferedImage capture = null;
		try {
			capture = robot.createScreenCapture(new Rectangle(screenWidth,
					screenHeight));
			// capture = loadImage("capture0.png");
		} catch (SecurityException ex) {
			System.out.println("ERROR: " + ex.getMessage());
			System.exit(-1);
		}

		Main.log("capture finished");

		// No need to process the image
		/*
		 * Main.log("capture process started");
		 * 
		 * ImagePlus imagePlus = new ImagePlus("base screen", capture);
		 * ImageConverter converter = new ImageConverter(imagePlus);
		 * converter.convertToGray8(); ImageProcessor processor =
		 * imagePlus.getProcessor(); processor.findEdges(); capture =
		 * imagePlus.getBufferedImage();
		 * 
		 * 
		 * Main.log("capture process finished");
		 */

		capture = Main.makeBinary(capture);

		Main.log("image write started");
		Main.writeImage(capture);
		Main.log("image write finished");

		return capture;
	}

	// gets the capture of screen and calculates and initializes grids on the
	// current screen
	private Screen getScreen() {

		BufferedImage capture = getCapture();

		if (capture != null) {

			/*
			 * int width = capture.getWidth(); int height = capture.getHeight();
			 * 
			 * int horizontalSearchIndex = height / 2; int verticalSearchIndex =
			 * width / 2; //int verticalSearchIndex = 540;
			 * 
			 * int currentColor;
			 * 
			 * // keeps the number grids on the horizontal screen int xGridCount
			 * = 0; int lastGridX = 0;
			 * 
			 * // keeps the number grids on the vertical screen int yGridCount =
			 * 0; int lastGridY = 0;
			 * 
			 * Point upperLeft = new Point(0, 0); Point bottomRight = new
			 * Point(0, 0);
			 * 
			 * // search for the horizontal grid points for (int i =
			 * gameStartPoint.x; i < gameEndPoint.x; i++) {
			 * 
			 * currentColor = capture.getRGB(i, horizontalSearchIndex);
			 * 
			 * Main.log("x: "+i+" current color: " + currentColor); // if upper
			 * left corner of the screen is not set than this is // upper left
			 * corner x if (currentColor == gridBorderColor && upperLeft.x == 0)
			 * { //check for random whiteness on the image so that check for 2
			 * up and 2 down colors if (isVerticalSame(gridBorderColor, capture,
			 * new Point(i, horizontalSearchIndex))) {
			 * Main.log("current color is equal to grid border"); upperLeft.x =
			 * i; //add border thickness i += borderThickness; } } else if
			 * (currentColor == gridBorderColor ) { if
			 * (isVerticalSame(gridBorderColor, capture, new Point(i,
			 * horizontalSearchIndex))) {
			 * Main.log("current color is equal to grid border"); xGridCount++;
			 * lastGridX = i; //add border thickness i += borderThickness; } } }
			 * 
			 * // set the bottom right corner of the x coordinate bottomRight.x
			 * = lastGridX;
			 * 
			 * // search for the vertical grid points for (int j =
			 * gameStartPoint.y; j < gameEndPoint.y; j++) {
			 * 
			 * currentColor = capture.getRGB(verticalSearchIndex, j);
			 * Main.log("y: "+j+" current color: " + currentColor); // if upper
			 * left corner of the screen is not set than this is // upper left
			 * corner x if (currentColor == gridBorderColor && upperLeft.y == 0)
			 * { //check for random whiteness on the image so that check for 2
			 * up and 2 down colors if (isHorizontalSame(gridBorderColor,
			 * capture, new Point(verticalSearchIndex, j))) {
			 * Main.log("current color is equal to grid border"); upperLeft.y =
			 * j; //add border thickness j += borderThickness; } } else if
			 * (currentColor == gridBorderColor ) { if
			 * (isHorizontalSame(currentColor, capture, new
			 * Point(verticalSearchIndex, j))) {
			 * Main.log("current color is equal to grid border"); yGridCount++;
			 * lastGridY = j; //add border thickness j += borderThickness; } } }
			 * 
			 * // set the bottom right corner of the y coordinate bottomRight.y
			 * = lastGridY;
			 */

			if (levelCounter >= 4)
				levelCounter = 4;

			Screen screen = new Screen(screenStartPoints[levelCounter],
					screenEndPoints[levelCounter], xCounts[levelCounter],
					yCounts[levelCounter], capture, baseImage);
			levelCounter++;
			return screen;
		}

		return null;

	}

	/*
	 * If the 2 up points and 2 down points of the given image starting from the
	 * base point is searched if the taken color is not same as the given color
	 * then this cannot be border
	 */
	private boolean isVerticalSame(int color, BufferedImage capture,
			Point basePoint) {

		int searchCount = 2;

		// down the base point search
		for (int i = 1; i <= searchCount; i++) {
			int currentColor = capture.getRGB(basePoint.x, basePoint.y
					+ (i * borderThickness));
			// if the taken color is not same as the border it is random border
			// point
			if (currentColor != color)
				return false;
		}

		// up the base point search
		for (int i = -1; i <= (searchCount * -1); i--) {
			int currentColor = capture.getRGB(basePoint.x, basePoint.y
					+ (i * borderThickness));
			// if the taken color is not same as the border it is random border
			// point
			if (currentColor != color)
				return false;
		}

		return true;
	}

	/*
	 * If the 2 up points and 2 down points of the given image starting from the
	 * base point is searched if the taken color is not same as the given color
	 * then this cannot be border
	 */
	private boolean isHorizontalSame(int color, BufferedImage capture,
			Point basePoint) {

		int searchCount = 2;

		// down the base point search
		for (int i = 1; i <= searchCount; i++) {
			int currentColor = capture.getRGB(basePoint.x
					+ (i * borderThickness), basePoint.y);
			// if the taken color is not same as the border it is random border
			// point
			if (currentColor != color)
				return false;
		}

		// up the base point search
		for (int i = -1; i <= (searchCount * -1); i--) {
			int currentColor = capture.getRGB(basePoint.x
					+ (i * borderThickness), basePoint.y);
			// if the taken color is not same as the border it is random border
			// point
			if (currentColor != color)
				return false;
		}

		return true;
	}

	// loads the base screen image from file
	private void loadBaseScreen() {
		baseImage = loadImage(baseScreenFileName);
		baseImage = Main.makeBinary(baseImage);
	}

	private BufferedImage loadImage(String fileName) {
		BufferedImage image = null;
		try {
			image = ImageIO.read(new File(fileName));
		} catch (IOException e) {
			System.out.println("ERROR: " + e.getMessage());
			System.exit(-1);
		}

		return image;
	}

	// set the current screen properties
	private void setScreen(Point upperLeft, Point downright, int xCount,
			int yCount) {
		this.upperLeft = upperLeft;
		this.downRight = downright;
		this.xCount = xCount;
		this.yCount = yCount;
	}

	// calculates the grid sizes according to current values of upperleft,
	// downright, xcount and ycount of the screen
	private void calGridSize() {
		xGridSize = (downRight.x - upperLeft.x) / xCount;
		yGridSize = (downRight.y - upperLeft.y) / yCount;
	}

	private void setGridContent() {

		contentPoints.clear();
	}

	// returns sample pixels points according to given x and y coordinate of
	// grids
	private ArrayList<Point> getSamplePixels(int x, int y) {

		ArrayList<Point> samples = new ArrayList<Point>();

		Point upperLeftPoint = getUpperLeftOfGrid(x, y);
		Point downRightPoint = getDownRightOfGrid(x, y);

		int unitSampleX = (downRightPoint.x - upperLeftPoint.x) / sampleCount;
		int unitSampleY = (downRightPoint.y - upperLeftPoint.y) / sampleCount;

		for (int i = 1; i <= sampleCount; i++) {
			for (int j = 1; j <= sampleCount; j++) {
				int sampleX = (unitSampleX * i) + upperLeftPoint.x;
				int sampleY = (unitSampleY * j) + upperLeftPoint.y;
				Point p = new Point(sampleX, sampleY);
				samples.add(p);
			}
		}

		return samples;
	}

	// returns the point which points to the upper left corner of the given grid
	// assumes that grid indexes start by 0
	private Point getUpperLeftOfGrid(int x, int y) {
		int upperX = (x * xGridSize) + upperLeft.x;
		int upperY = (y * yGridSize) + upperLeft.y;

		return new Point(upperX, upperY);
	}

	// returns the point which points to the down right corner of the given grid
	// assumes that grid indexes start by 0
	private Point getDownRightOfGrid(int x, int y) {
		int upperX = ((x + 1) * xGridSize) + upperLeft.x;
		int upperY = ((y + 1) * yGridSize) + upperLeft.y;

		return new Point(upperX, upperY);
	}

	// waits the execution for a given millisecond
	private void wait(int millisecond) {
		try {
			Thread.sleep(millisecond);
		} catch (InterruptedException ex) {
			System.out.println("ERROR: " + ex.getMessage());
			System.exit(-1);
		}

	}
}
